#include <assert.h>
#include <gc/gc.h>

#include "types.h"
#include "generic-list.h"

void *new_iterate_first(iterator_t *iterator, generic_list_t *list)
{
  value_t *first = NULL;
  
  if(0 == list->length)
  {
    iterator->first = NULL;
    iterator->last = NULL;
  }
  else
  {
    iterator->first = list->first;
    iterator->last = list->last;
  }
  
  iterator->current = iterator->first;
  if(iterator->current)
    first = (iterator->current)->head;

  return first;
}

iterator_t *get_iterator(generic_list_t *list)
{
  iterator_t *iterator = GC_MALLOC(sizeof(iterator_t));

  if(0 == list->length)
  {
    iterator->first = NULL;
    iterator->last = NULL;
  }
  else
  {
    iterator->first = list->first;
    iterator->last = list->last;
  }
  
  iterator->current = NULL;

  return iterator;
}

void *iterate_first(iterator_t *iterator)
{
  value_t *first = NULL;

  iterator->current = iterator->first;
  if(iterator->current)
    first = (iterator->current)->head;

  return first;
}

void *iterate_next(iterator_t *iterator)
{
  value_t *next = NULL;

  iterator->current = (iterator->current)->tail;
  if(iterator->current)
    next = (iterator->current)->head;

  return next;
}

int iterate_is_valid(iterator_t *iterator)
{
  return (NULL != iterator->current);
}

int iterate_at_end(iterator_t *iterator)
{
  return (iterator->current == iterator->last);
}

/*
  generic_list_t in general needs to be more functional.
  we're probably changing much more state than necessary
  - particularly making superfluous copies of lists
*/


void *generic_list_index(generic_list_t *value, int n)
{
  int i;
  list_entry_t *item = value->first;
  for(i = 0; i < n; i++)
    item = item->tail;
  return item->head;
}

/*
  Takes a list_entry_t *and creates a list header starting
  from that point.
  
  Should lengths be recalculated lazily?
*/

generic_list_t *splice_generic_list(list_entry_t *pivot)
{
  generic_list_t *new_list = (generic_list_t*)allocate_type(type_generic_list);
  

  new_list->first = pivot;
  
  int length = 1;
  
  if(!pivot)
  {
    new_list->length = 0;
    new_list->first = 0;
    new_list->last = 0;
    return new_list;
  }
  
  while(pivot->tail)
  {
    pivot = pivot->tail;
    length++;
  }
  
  new_list->last = pivot;
  new_list->length = length;
  
  return new_list;
}

/*
  Creates a new generic list with a given head. If head is NULL, length is
  initialised to 0, else 1.
*/

generic_list_t *make_generic_list(void *head)
{
  // WORK NEEDED
  // Allocate our own storage to avoid the current 'data' cludge in allocate_type
  generic_list_t *new_list = GC_MALLOC(sizeof(generic_list_t));
  new_list->type = type_generic_list;

  assert(new_list);

  new_list->length = 0;
  new_list->first = 0;
  new_list->last  = 0;
  
  if(head)
    generic_list_add(new_list, head);
  
  return new_list;
}

generic_list_t *generic_list_copy(generic_list_t *list)
{
  if(!list)
    return NULL;

  size_t number_of_items = list->length;
  void *list_buffer = GC_MALLOC(sizeof(generic_list_t)
                                  + (number_of_items * sizeof(list_entry_t)));

  generic_list_t *copy = list_buffer;

  copy->type = type_generic_list;
  copy->length = number_of_items;

  list_entry_t *previous;
  list_entry_t *current = previous = (list_entry_t*)(copy + 1);

  iterator_t stack_allocated;
  iterator_t *list_items = &stack_allocated; //get_iterator(list);
  value_t *item, *value;

  int i = 0;

  copy->first = current; 

  for(item = new_iterate_first(list_items, list);
        iterate_is_valid(list_items);
        item = iterate_next(list_items))
  {
    current->head = item;
    current->tail = (current + 1);
    previous = current++;
  }

  previous->tail = 0;
  copy->last = previous;

  return copy;
}
/*
generic_list_t *generic_list_copy(generic_list_t *list)
{
  // We know the size of this list!
  // - can we make g_l_t polymorphic to avoid allocating
  // lots of little fragmented l_e_t's?
	if(list)
		assert(type_generic_list == list->type);
  else
    return NULL;
	generic_list_t *new_list = //(generic_list_t*)allocate_type(type_generic_list);
    make_generic_list(NULL);
  new_list->length = list->length;

  if(0 == list->length)
  {
    new_list->first = 0;
    new_list->last = 0;
    return new_list;
  }
  
  list_entry_t *current = list->first;
  list_entry_t *new, *previous = 0;
  
  while(current)
  {
    new = GC_MALLOC(sizeof(list_entry_t));
    if(!new_list->first)
    {
      new_list->first = new;
    }
    if(previous)
    {
      previous->tail = new;
    }
    new->head = current->head;
    previous = new;
    current = current->tail;
  }

  previous->tail = 0;
  new_list->last = previous;
  return new_list;
}
*/

generic_list_t *generic_list_append(generic_list_t *a, generic_list_t *b)
{
	if(a)
		assert(type_generic_list == a->type);
	if(b)
		assert(type_generic_list == b->type);

  if(!a || !a->length)
  {
    return b ? generic_list_copy(b) : NULL;
  }
  
  if(!b || !b->length)
  {
    return a ? generic_list_copy(a) : NULL;
  }
  
  generic_list_t *new_list = generic_list_copy(a);
  generic_list_t *append_list = generic_list_copy(b);
  
  if(!new_list->first)
  {
    assert(!new_list->last);
    new_list->first = append_list->first;
  }
  
  new_list->length += append_list->length;
  
  if(new_list->last)
  {
    new_list->last->tail = append_list->first;
  }
  
  if(append_list->last)
  {
    new_list->last = append_list->last;
  }
  
  // free(append_list);
  
  return new_list;
}

// Sort these out - their names are non-obvious

/*
  Adds an item to the end of the list and updates the length accordingly. As
  we have no size information, 'value' must be heap allocated by the caller.
*/

void generic_list_add(generic_list_t *list, void *value)
{
  
  if(0 == list->length)
  {
    list->first = GC_MALLOC(sizeof(list_entry_t));
    list->first->head = value;
    list->first->tail = NULL;
    list->last = list->first;
  }
  else
  {
    list_entry_t *new_entry = GC_MALLOC(sizeof(list_entry_t));
    new_entry->head = value;
    new_entry->tail = NULL;
    list->last->tail = new_entry;
    list->last = new_entry;
  }
  
  list->length++;
  
  return;
}

void generic_list_insert(generic_list_t *list, void *value)
{
  if(0 == list->length)
  {
    list->first = GC_MALLOC(sizeof(list_entry_t));
    list->first->head = value;
    list->first->tail = NULL;
    list->last = list->first;
  }
  else
  {
    list_entry_t *new_entry = GC_MALLOC(sizeof(list_entry_t));
    new_entry->head = value;
    new_entry->tail = list->first;
    list->first = new_entry;
  }

  list->length++;
}

/*
  Removes the value at the start of the list and updates the length
  accordingly. We free the list_entry_t, but it's the caller's
  responsibility to free the value when it's no longer of use.
*/

void *generic_list_remove(generic_list_t *list)
{
  void *value;
  
  list_entry_t *head = list->first;
  value = head->head;
  
  list->first = head->tail;
  if(list->last == head)
    list->last = NULL;
  
  //free(head);
  
  list->length--;
  
  return value;
}

